package com.new1cloud.upload.server.service.bootstrap.handler;

import com.new1cloud.file.config.UploadConfig;
import com.new1cloud.file.domain.*;
import com.new1cloud.file.tools.ThatStringUtil;
import com.new1cloud.file.upload.client.entry.ChunkFile;
import com.new1cloud.upload.server.service.FileUploadService;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.Cleanup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileOutputStream;
import java.util.Objects;
import java.util.concurrent.ExecutorService;

/**
 * 处理{@link ChunkFile}类型消息的Handler
 */
public class FileUploadServerHandler extends ChannelInboundHandlerAdapter {
    protected final Logger log = LoggerFactory.getLogger(this.getClass());

    private final FileUploadService fileUploadService;
    private final ExecutorService executorService;

    public FileUploadServerHandler(FileUploadService fileUploadService, ExecutorService executorService) {
        this.fileUploadService = fileUploadService;
        this.executorService = executorService;
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        executorService.execute(() -> {
            if (msg instanceof FileUploadBasicInfo) {
                log.info("收到来自客户端发送的FileUploadBasicInfo信息.");
                FileUploadBasicInfo basicInfo = (FileUploadBasicInfo) msg;
                //检查该文件的上传进度情况(一般是不会返回NULL)
                FileUploadProcess fileUploadProcess = fileUploadService.checkUploadProcess(basicInfo.getFileHash(), basicInfo.getFileSize(),
                        basicInfo.getFileName(), basicInfo.getUploadPath());
                if (Objects.isNull(fileUploadProcess)) {
                    //fileSize为-1表示服务端未查询到上传进度信息
                    fileUploadProcess = new FileUploadProcess(basicInfo.getFileName(), -1L, basicInfo.getFileHash(), 0, null,
                            UploadConfig.getChunkSize(), "");
                }
                ctx.writeAndFlush(SendChunkFilesReq.builder().fileUploadProcess(fileUploadProcess).needSendChunkFiles(basicInfo.getNeedSendChunkFiles()).build());
            } else if (msg instanceof ChunkFile) {
                ChunkFile chunkFile = (ChunkFile) msg;
                log.info("服务端成功接收到分片文件: {}", chunkFile);
                //检查该文件的上传进度情况
                FileUploadProcess fileUploadProcess = fileUploadService.checkUploadProcess(chunkFile.getFileHash(), chunkFile.getFileSize(),
                        chunkFile.getFileName(), chunkFile.getUploadDir());
                if (Objects.isNull(fileUploadProcess)) {
                    //这种情况几乎不会出现
                    log.error("文件({}，唯一标识码: {})在处理分片({})时执行checkUploadProcess异常.", chunkFile.getFileName(), chunkFile.getFileHash(), chunkFile.getChunkName());
                    return;
                }
                if (Objects.equals(fileUploadProcess.getUploadStatus(), 1)) {
                    log.info("文件({}，唯一标识码: {})已聚合完成，将忽略本次获取到的分片文件", chunkFile.getFileName(), chunkFile.getFileHash());
                    ctx.writeAndFlush(fileUploadProcess);
                    return;
                }
                //将分片文件落地到指定的临时目录下
                String chunkFileDir = ThatStringUtil.urlConcat(UploadConfig.getTemPath(), chunkFile.getFileHash());
                String chunkFilePath = ThatStringUtil.urlConcat(chunkFileDir, chunkFile.getChunkName());

                File chunkFileObj = new File(chunkFilePath);
                if (chunkFileObj.exists()) {
                    //该分片文件已存在，无需继续往下执行
                    log.error("文件({}，唯一标识码: {})的分片文件({})已存在，无需再进行生成。", chunkFile.getFileName(), chunkFile.getFileHash(), chunkFile.getChunkName());
                    ctx.writeAndFlush(ChunkFileUploadStatus.convertFromChunkFile(chunkFile, fileUploadProcess, 1));
                    return;
                }

                File temChunkFile = new File(chunkFilePath + ".doing");
                //标识该分片文件已经在生成中（通过其他线程），忽略本次保存
                if (temChunkFile.exists()) {
                    log.error("文件({}，唯一标识码: {})的分片文件({})正在生成中，无需再进行生成。", chunkFile.getFileName(), chunkFile.getFileHash(), chunkFile.getChunkName());
                    ctx.writeAndFlush(ChunkFileUploadStatus.convertFromChunkFile(chunkFile, fileUploadProcess, 1));
                    return;
                }
                try {
                    if (temChunkFile.createNewFile()) {
                        @Cleanup FileOutputStream fileOutputStream = new FileOutputStream(chunkFileObj, false);
                        fileOutputStream.write(chunkFile.getChunkData());
                        fileOutputStream.close();
                        //生成完成之后，删除临时分片文件
                        temChunkFile.delete();
                        log.info("文件({}，唯一标识码: {})的分片文件({})已成功生成.", chunkFile.getFileName(), chunkFile.getFileHash(), chunkFile.getChunkName());
                    } else {
                        log.error("文件({}，唯一标识码: {})的分片文件({})生成临时doing文件失败, 请手动创建该临时文件，并通知客户端重传该分片文件.",
                                chunkFile.getFileName(), chunkFile.getFileHash(), chunkFile.getChunkName());
                        ctx.writeAndFlush(ChunkFileUploadStatus.convertFromChunkFile(chunkFile, fileUploadProcess, 0));
                        return;
                    }
                } catch (Exception e) {
                    log.error("文件({}，唯一标识码: {})的分片文件({})生成临时目录和文件异常, ", chunkFile.getFileName(), chunkFile.getFileHash(), chunkFile.getChunkName(), e);
                    return;
                } finally {
                    //释放内存
                    chunkFile.setChunkData(null);
                    System.gc();
                }
                //再次检测分片文件是否齐全
                fileUploadProcess = fileUploadService.checkUploadProcess(chunkFile.getFileHash(), chunkFile.getFileSize(),
                        chunkFile.getFileName(), chunkFile.getUploadDir());
                if (Objects.isNull(fileUploadProcess)) {
                    //忽略，几乎不会出现这种情况
                    log.info("文件({}，唯一标识码: {})在处理分片({})时第二次执行checkUploadProcess异常.", chunkFile.getFileName(), chunkFile.getFileHash(), chunkFile.getChunkName());
                    return;
                }
                log.debug("文件({})当前已完成上传的分片文件: {}", chunkFile.getFileName(), fileUploadProcess.getCompletedChunkItem());
//                //检查是否所有分片文件都已齐全了，如果是的则进行聚合操作(因为需考虑到断点续传的场景)
//                if (FileUploadProcess.checkCompleted(fileUploadProcess)) {
//                    fileUploadService.mergeChunkFiles(chunkFile.getFileHash(), chunkFile.getFileSize(), chunkFile.getUploadDir(), chunkFile.getFileName());
//                    log.info("已完成聚合文件({}，唯一标识码: {})的所有分片文件.", chunkFile.getFileName(), chunkFile.getFileHash());
//                }
                ChunkFileUploadStatus chunkFileUploadStatus = ChunkFileUploadStatus.convertFromChunkFile(chunkFile, fileUploadProcess, 1);
                log.info("发送给客户端chunkFileUploadStatus: {}", chunkFileUploadStatus);
                ctx.writeAndFlush(chunkFileUploadStatus);

            } else if(msg instanceof UploadCompleteMark) {
                log.info("服务端成功接收到UploadCompleteMark格式消息");
                UploadCompleteMark completeMark = (UploadCompleteMark) msg;
                fileUploadService.mergeChunkFiles(completeMark.getFileHash(), completeMark.getFileSize(), completeMark.getUploadPath(), "");
                log.info("已完成聚合文件({}，唯一标识码: {})的所有分片文件.", completeMark.getFileName(), completeMark.getFileHash());

            } else {
                log.error("服务端[FileUploadServerHandler]收到非预期类型的消息:{},将被丢弃!", msg);
            }
        });
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        super.exceptionCaught(ctx, cause);
        log.error("服务端发生异常[FileUploadServerHandler]: ", cause);
        ctx.close();
    }
}
